/*
 * Create a graphviz graph based on the classes in the specified java
 * source files.
 *
 * (C) Copyright 2002-2010 Diomidis Spinellis
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * $Id: UmlGraph.java,v 1.84 2010/05/24 05:46:57 dds Exp $
 *
 */

package org.umlgraph.doclet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.RootDoc;

/**
 * Doclet API implementation
 * 
 * @depend - - - OptionProvider
 * @depend - - - Options
 * @depend - - - View
 * @depend - - - ClassGraph
 * @depend - - - Version
 * 
 * @version $Revision: 1.84 $
 * @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a>
 */
public class UmlGraph {

	private static final String programName = "UmlGraph";
	private static final String docletName = "org.umlgraph.doclet.UmlGraph";

	/** Options used for commenting nodes */
	private static Options commentOptions;

	/** Entry point through javadoc */
	public static boolean start(RootDoc root) throws IOException {
		Options opt = buildOptions(root);
		root.printNotice("UMLGraph doclet version " + Version.VERSION + " started");

		View[] views = buildViews(opt, root, root);
		if (views == null) {
			return false;
		}
		if (views.length == 0) {
			buildGraph(root, opt, null);
		} else {
			for (int i = 0; i < views.length; i++) {
				buildGraph(root, views[i], null);
			}
		}
		return true;
	}

	public static void main(String args[]) {
		PrintWriter err = new PrintWriter(System.err);
		com.sun.tools.javadoc.Main.execute(programName, err, err, err, docletName, args);
	}

	public static Options getCommentOptions() {
		return commentOptions;
	}

	/**
	 * Creates the base Options object. This contains both the options specified
	 * on the command line and the ones specified in the UMLOptions class, if
	 * available. Also create the globally accessible commentOptions object.
	 */
	public static Options buildOptions(RootDoc root) {
		commentOptions = new Options();
		commentOptions.setOptions(root.options());
		commentOptions.setOptions(findClass(root, "UMLNoteOptions"));
		commentOptions.shape = new Shape("note");

		Options opt = new Options();
		opt.setOptions(root.options());
		opt.setOptions(findClass(root, "UMLOptions"));
		return opt;
	}

	/** Return the ClassDoc for the specified class; null if not found. */
	private static ClassDoc findClass(RootDoc root, String name) {
		ClassDoc[] classes = root.classes();
		for (ClassDoc cd : classes)
			if (cd.name().equals(name))
				return cd;
		return null;
	}

	/**
	 * Builds and outputs a single graph according to the view overrides
	 */
	public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException {
		if (getCommentOptions() == null)
			buildOptions(root);
		Options opt = op.getGlobalOptions();
		root.printNotice("Building " + op.getDisplayName());
		ClassDoc[] classes = root.classes();

		ClassGraph c = new ClassGraph(root, op, contextDoc);
		c.prologue();
		for (int i = 0; i < classes.length; i++)
			c.printClass(classes[i], true);
		for (int i = 0; i < classes.length; i++)
			c.printRelations(classes[i]);
		if (opt.inferRelationships)
			c.printInferredRelations(classes);
		if (opt.inferDependencies)
			c.printInferredDependencies(classes);

		c.printExtraClasses(root);
		c.epilogue();
	}

	/**
	 * Builds the views according to the parameters on the command line
	 * 
	 * @param opt
	 *            The options
	 * @param srcRootDoc
	 *            The RootDoc for the source classes
	 * @param viewRootDoc
	 *            The RootDoc for the view classes (may be different, or may be
	 *            the same as the srcRootDoc)
	 */
	public static View[] buildViews(Options opt, RootDoc srcRootDoc, RootDoc viewRootDoc) {
		if (opt.viewName != null) {
			ClassDoc viewClass = viewRootDoc.classNamed(opt.viewName);
			if (viewClass == null) {
				System.out.println("View " + opt.viewName + " not found! Exiting without generating any output.");
				return null;
			}
			if (viewClass.tags("view").length == 0) {
				System.out.println(viewClass + " is not a view!");
				return null;
			}
			if (viewClass.isAbstract()) {
				System.out.println(viewClass + " is an abstract view, no output will be generated!");
				return null;
			}
			return new View[] { buildView(srcRootDoc, viewClass, opt) };
		} else if (opt.findViews) {
			List<View> views = new ArrayList<View>();
			ClassDoc[] classes = viewRootDoc.classes();

			// find view classes
			for (int i = 0; i < classes.length; i++)
				if (classes[i].tags("view").length > 0 && !classes[i].isAbstract())
					views.add(buildView(srcRootDoc, classes[i], opt));

			return views.toArray(new View[views.size()]);
		} else
			return new View[0];
	}

	/**
	 * Builds a view along with its parent views, recursively
	 */
	private static View buildView(RootDoc root, ClassDoc viewClass, OptionProvider provider) {
		ClassDoc superClass = viewClass.superclass();
		if (superClass == null || superClass.tags("view").length == 0)
			return new View(root, viewClass, provider);

		return new View(root, viewClass, buildView(root, superClass, provider));
	}

	/** Option checking */
	public static int optionLength(String option) {
		return Options.optionLength(option);
	}

	/** Indicate the language version we support */
	public static LanguageVersion languageVersion() {
		return LanguageVersion.JAVA_1_5;
	}
}
